/*
Copyright (C) 2011 The University of Michigan
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Please send inquiries to powertutor@umich.edu
*/
package edu.umich.PowerTutor.ui;
import org.achartengine.GraphicalView;
import org.achartengine.chart.CubicLineChart;
import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.model.XYSeries;
import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import edu.umich.PowerTutor.service.ICounterService;
import edu.umich.PowerTutor.service.PowerEstimator;
import edu.umich.PowerTutor.service.UMLoggerService;
import edu.umich.PowerTutor.util.SystemInfo;
public class PowerViewer extends Activity {
private static final String TAG = "PowerViewer";
private SharedPreferences prefs;
private int uid;
private int components;
private String[] componentNames;
private int[] componentsMaxPower;
private int noUidMask;
private boolean collecting;
private ValueCollector[] collectors;
private Intent serviceIntent;
private CounterServiceConnection conn;
private ICounterService counterService;
private Handler handler;
private LinearLayout chartLayout;
public void refreshView() {
if(counterService == null) {
TextView loadingText = new TextView(this);
loadingText.setText("Waiting for profiler service...");
loadingText.setGravity(Gravity.CENTER);
setContentView(loadingText);
return;
}
chartLayout = new LinearLayout(this);
chartLayout.setOrientation(LinearLayout.VERTICAL);
if(uid == SystemInfo.AID_ALL) {
/* If we are reporting global power usage then just set noUidMask to 0 so
* that all components get displayed.
*/
noUidMask = 0;
}
components = 0;
for(int i = 0; i < componentNames.length; i++) {
if((noUidMask & 1 << i) == 0) {
components++;
}
}
boolean showTotal = prefs.getBoolean("showTotalPower", false);
collectors = new ValueCollector[(showTotal ? 1 : 0) + components];
int pos = 0;
for(int i = showTotal ? -1 : 0; i < componentNames.length; i++) {
if(i != -1 && (noUidMask & 1 << i) != 0) {
continue;
}
String name = i == -1 ? "Total" : componentNames[i];
double mxPower = (i == -1 ? 2100.0 : componentsMaxPower[i]) * 1.05;
XYSeries series = new XYSeries(name);
XYMultipleSeriesDataset mseries = new XYMultipleSeriesDataset();
mseries.addSeries(series);
XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
XYSeriesRenderer srenderer = new XYSeriesRenderer();
renderer.setYAxisMin(0.0);
renderer.setYAxisMax(mxPower);
renderer.setYTitle(name + "(mW)");
int clr = PowerPie.COLORS[(PowerPie.COLORS.length + i) %
PowerPie.COLORS.length];
srenderer.setColor(clr);
srenderer.setFillBelowLine(true);
srenderer.setFillBelowLineColor(((clr >> 1) & 0x7F7F7F) |
(clr & 0xFF000000));
renderer.addSeriesRenderer(srenderer);
View chartView = new GraphicalView(this,
new CubicLineChart(mseries, renderer, 0.5f));
chartView.setMinimumHeight(100);
chartLayout.addView(chartView);
collectors[pos] = new ValueCollector(series, renderer, chartView, i);
if(handler != null) {
handler.post(collectors[pos]);
}
pos++;
}
/* We're giving 100 pixels per graph of vertical space for the chart view.
If we don't specify a minimum height the chart view ends up having a
height of 0 so this is important. */
chartLayout.setMinimumHeight(100 * components);
ScrollView scrollView = new ScrollView(this) {
public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
return true;
}
};
scrollView.addView(chartLayout);
scrollView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
setContentView(scrollView);
}
private class CounterServiceConnection implements ServiceConnection {
public void onServiceConnected(ComponentName className,
IBinder boundService) {
counterService = ICounterService.Stub.asInterface((IBinder)boundService);
try {
componentNames = counterService.getComponents();
componentsMaxPower = counterService.getComponentsMaxPower();
noUidMask = counterService.getNoUidMask();
refreshView();
} catch(RemoteException e) {
counterService = null;
}
}
public void onServiceDisconnected(ComponentName className) {
counterService = null;
getApplicationContext().unbindService(conn);
getApplicationContext().bindService(serviceIntent, conn, 0);
Log.w(TAG, "Unexpectedly lost connection to service");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
prefs = PreferenceManager.getDefaultSharedPreferences(this);
uid = getIntent().getIntExtra("uid", SystemInfo.AID_ALL);
collecting = true;
if(savedInstanceState != null) {
collecting = savedInstanceState.getBoolean("collecting", true);
componentNames = savedInstanceState.getStringArray("componentNames");
noUidMask = savedInstanceState.getInt("noUidMask");
}
serviceIntent = new Intent(this, UMLoggerService.class);
conn = new CounterServiceConnection();
}
@Override
protected void onResume() {
super.onResume();
handler = new Handler();
getApplicationContext().bindService(serviceIntent, conn, 0);
refreshView();
}
@Override
protected void onPause() {
super.onPause();
getApplicationContext().unbindService(conn);
if(collectors != null) for(int i = 0; i < components; i++) {
handler.removeCallbacks(collectors[i]);
}
counterService = null;
handler = null;
collecting = true;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("collecting", collecting);
outState.putStringArray("componentNames", componentNames);
outState.putInt("noUidMask", noUidMask);
}
/* Let all of the UI graphs lay themselves out again. */
private void stateChanged() {
for(int i = 0; i < components; i++) {
collectors[i].layout();
}
}
private static final int MENU_OPTIONS = 0;
private static final int MENU_TOGGLE_COLLECTING = 1;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, MENU_OPTIONS, 0, "Options");
menu.add(0, MENU_TOGGLE_COLLECTING, 0, "");
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(MENU_TOGGLE_COLLECTING).setTitle(
collecting ? "Pause" : "Resume");
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case MENU_OPTIONS:
startActivity(new Intent(this, ViewerPreferences.class));
return true;
case MENU_TOGGLE_COLLECTING:
collecting = !collecting;
if(handler != null) {
if(collecting) for(int i = 0; i < components; i++) {
collectors[i].reset();
handler.post(collectors[i]);
} else for(int i = 0; i < components; i++) {
handler.removeCallbacks(collectors[i]);
}
}
break;
}
return false;
}
public class ValueCollector implements Runnable {
private XYSeries series;
private XYMultipleSeriesRenderer renderer;
private View chartView;
private int componentId;
private long lastTime;
int[] values;
private boolean readHistory;
public ValueCollector(XYSeries series, XYMultipleSeriesRenderer renderer,
View chartView, int componentId) {
this.series = series;
this.renderer = renderer;
this.chartView = chartView;
this.componentId = componentId;
lastTime = SystemClock.elapsedRealtime();
layout();
}
public void layout() {
int numVals = Integer.parseInt(prefs.getString("viewNumValues_s", "60"));
values = new int[numVals];
renderer.clearXTextLabels();
renderer.setXAxisMin(0);
renderer.setXAxisMax(numVals - 1);
renderer.addXTextLabel(numVals - 1, "" + numVals);
renderer.setXLabels(0);
for(int j = 0; j < 10; j++) {
renderer.addXTextLabel(numVals * j / 10, "" + (1 + numVals * j / 10));
}
reset();
}
/** Restart points collecting from zero. */
public void reset() {
series.clear();
readHistory = true;
}
public void run() {
int numVals = Integer.parseInt(prefs.getString("viewNumValues_s", "60"));
if(counterService != null) try {
if(readHistory) {
values = counterService.getComponentHistory(numVals,
componentId, uid);
readHistory = false;
} else {
for(int i = numVals - 1; i > 0; i--) {
values[i] = values[i - 1];
}
values[0] = counterService.getComponentHistory(1, componentId,
uid)[0];
}
} catch(RemoteException e) {
Log.w(TAG, "Failed to get data from service");
for(int i = 0; i < numVals; i++) {
values[i] = 0;
}
}
series.clear();
for(int i = 0; i < numVals; i++) {
series.add(i, values[i]);
}
long curTime = SystemClock.elapsedRealtime();
long tryTime = lastTime + PowerEstimator.ITERATION_INTERVAL *
(long)Math.max(1, 1 + (curTime - lastTime) /
PowerEstimator.ITERATION_INTERVAL);
if(handler != null) {
handler.postDelayed(this, tryTime - curTime);
}
chartView.invalidate();
}
};
}